#!/usr/bin/env bash

Version="1.10"
#获取工作目录
BaseDir="$(cd $(dirname "$0"); pwd -P)"
#默认配置文件
ConfigFile="wechatpush.conf"

#初始化默认参数
function Initialization() {
#运行时参数
	CURL_CMD="/usr/bin/env curl -s"
	Retry="3"
	Interval="1"
	StatusFile="/tmp/wechat.push.status"
#微信API接口
	URL_API="https://api.weixin.qq.com/cgi-bin"
	URL_Token="<URL_API>/token?grant_type=client_credential&appid=<WeChatAppID>&secret=<WeChatAppSecret>"
	URL_User="<URL_API>/user/get?access_token=<AccessToken>"
	URL_Message="<URL_API>/message/template/send?access_token=<AccessToken>"
	URL_Quota="<URL_API>/clear_quota?access_token=<AccessToken>"
	PostStructure="{\"touser\":\"<Openid>\",\"template_id\":\"<WeChatTemplateID>\",\"topcolor\":\"#FF0000\",\"data\":<Message>,\"url\":\"<Url>\"}"
#公众号配置
	WeChatAppID=""
	WeChatAppSecret=""
	WeChatTemplateID=""
	WeChatUserList=""
#实例信息
	ExampleMessage="{\"Time\":{\"color\":\"#0000CC\",\"value\":\"测试时间\"},\"Source\":{\"color\":\"#009900\",\"value\":\"测试主机\"},\"Type\":{\"color\":\"#CC0000\",\"value\":\"提醒\"},\"Overview\":{\"color\":\"#0000CC\",\"value\":\"概况\"},\"Details\":{\"color\":\"#009900\",\"value\":\"详情\"}}"
	ExampleUrl="http://www.baidu.com"
}

function _red() { echo "\033[1;31m${1}\033[0m"; }
function _green() { echo "\033[1;32m${1}\033[0m"; }
function _yellow() { echo "\033[1;33m${1}\033[0m"; }
function _blue() { echo "\033[1;34m${1}\033[0m"; }
function _purple() { echo "\033[1;35m${1}\033[0m"; }
function _cyan() { echo "\033[1;36m${1}\033[0m"; }
function _white() { echo "\033[1;37m${1}\033[0m"; }

#显示使用说明
function Usage() {
	echo -e "                ----==== $(_cyan "微信公众号消息推送脚本") ====----\n"
	[[ -n "${2}" ]] && echo -e "[$(_white "参数错误")] ${2}\n"
	echo -e "使用说明:"
	echo -e "  ${0} [选项] <推送内容> [链接地址]\n"
	echo -e "如果已经将脚本路径加入到环境变量，也可直接使用："
	echo -e "  ${0##*/} [选项] <推送内容> [链接地址]\n"
	echo -e "命令选项说明\n\n参数:"
	echo -e " -c, --config <文件路径>        加载指定配置文件，如使用了\"-d\"则该选项无效"
	echo -e " -d, --default                  不加载配置文件，而是使用内置的参数"
	echo -e " -q, --quiet                    静默模式，不显示脚本运行状态"
	echo -e " -e, --example                  推送内置或配置文件指定的实例信息"
	echo -e " -C, --clear                    重置接口调用次数(错误 45009)"
	echo
	echo -e " -l, --curl <文件路径>          使用指定的 curl 命令"
	echo -e " -r, --retry <数字>             失败重试次数(仅访问令牌过期或无效时)"
	echo -e " -i, --interval <数字>          重试间隔时间(秒)"
	echo -e " -f, --status <文件路径>         访问令牌保存位置"
	echo
	echo -e " -a, --appid <字符串>           推送时使用的公众号AppID"
	echo -e " -s, --secret <字符串>          推送时使用的公众号AppSecret"
	echo -e " -t, --template <字符串>        推送时使用的公众号TemplateID"
	echo -e " -u, --userlist <字符串>        推送用户列表，用\",\"进行分割"
	echo
	echo -e " -A, --url-api <字符串>         微信公众号API接口地址"
	echo -e " -T, --url-token <字符串>       微信公众号访问令牌接口地址"
	echo -e " -U, --url-user <字符串>        微信公众号用户列表接口地址"
	echo -e " -M, --url-message <字符串>     微信公众号消息推送接口地址"
	echo -e " -R, --url-quota <字符串>       微信公众号重置限额接口地址"
	echo -e " -S, --structure <字符串>       微信公众号消息推送数据结构"
	echo
	echo -e " -v, --version                  显示脚本的版本并退出"
	echo -e " -h, --help                     显示当前内容并退出"
	echo -e "\n$(_white "参数优先级说明:")\n"
	echo -e " 命令行参数优先级最高，其次是配置文件，脚本内置参数优先级最低。"
	echo -e " 如果重复设置了参数值，将会以优先级高的值为准。在优先级相同的情况"
	echo -e " 下，例如在命令行中重复设置了--appid 参数，则以最后设置的值为准。\n"
	exit ${1}
}

#分析命令参数
function ParseParameters() {
	Parameters="$(getopt -o a:A:c:Cdef:hi:l:M:qr:R:s:S:t:T:u:U:v -al config:,default,quiet,example,clear,curl:,retry:,interval:,status:,appid:,secret:,template:,userlist:,url-api:,url-token:,url-user:,url-message:,url-quota:,structure:,version,help -n "${0##*/}" -- "$@" 2>&1)"
	[[ ${?} -ne "0" ]] && Usage 1 "${Parameters%\'*}'"
}

#显示运行状态
function ShowStatus() {
	[[ -z "${Quiet}" ]] && echo -e "${1}"
}

#加载配置文件
function GetConfig() {
	eval set -- "${Parameters}"
	while [[ -n "${1}" ]]; do
		case ${1} in
		-c|-config|--config)
			shift; [[ -n "${1}" ]] && ConfigFile="${1}" ;;
		-d|-default|--default)
			ConfigFile="" && break ;;
		esac; shift
	done
	if [[ -n "${ConfigFile}" ]]; then
		[[ -n "${ConfigFile%%/*}" ]] && ConfigFile="${BaseDir}/${ConfigFile}"
		[[ -r "${ConfigFile}" ]] && while read line; do eval "$line"; done < "${ConfigFile}" || ShowStatus "[$(_yellow "提醒")] 读取配置文件\"${ConfigFile}\"失败，使用默认参数。"
	fi
}

#设置运行参数
function GetParameters() {
	eval set -- "${Parameters}"
	while [[ -n ${1} ]]; do
		case ${1} in
		-q|-quiet|--quiet)
			Quiet="YES"
			;;
		-e|-example|--example)
			Example="YES"
			;;
		-C|-clear|--clear)
			Clear="YES"
			;;
		-l|-curl|--curl)
			shift
			CURL_CMD="$1"
			;;
		-r|-retry|--retry)
			shift
			Retry="$1"
			;;
		-i|-interval|--interval)
			shift
			Interval="$1"
			;;
		-f|-status|--status)
			shift
			StatusFile="$1"
			;;
		-a|-appid|--appid)
			shift
			WeChatAppID="$1"
			;;
		-s|-secret|--secret)
			shift
			WeChatAppSecret="$1"
			;;
		-t|-template|--template)
			shift
			WeChatTemplateID="$1"
			;;
		-u|-userlist|--userlist)
			shift
			WeChatUserList="$1"
			;;
		-A|-url-api|--url-api)
			shift
			URL_API="$1"
			;;
		-T|-url-token|--url-token)
			shift
			URL_Token="$1"
			;;
		-U|-url-user|--url-user)
			shift
			URL_User="$1"
			;;
		-M|-url-message|--url-message)
			shift
			URL_Message="$1"
			;;
		-R|-url-quota|--url-quota)
			shift
			URL_Quota="$1"
			;;
		-S|-structure|--structure)
			shift
			PostStructure="$1"
			;;
		-v|-version|--version)
			echo "${0##*/} V${Version}"
			exit
			;;
		-h|-help|--help)
			Usage
			;;
		--)
			shift
			break
			;;
		esac
		shift
	done
	[[ -n "${Clear}" ]] && ClearQuota
	if [[ -z "${Example}" ]]; then
		[[ -n "${1}" ]] && Message="${1}"
		[[ -n "${2}" ]] && Url="${2}"
	else
		Message="${ExampleMessage}"
		Url="${ExampleUrl}"
	fi
	[[ -z "${Message}" ]] && Usage 1 "请输入推送内容！"
}

#读取推送状态
function ReadStatus() {
	StatusResult="$(echo "${PushStatus}" | grep "^${1}='.*'" | cut -d $'\'' -f 2-)"
	echo -n "${StatusResult%\'}"
	[[ -z "${StatusResult}" ]] && echo -n "${2}"
}

#保存推送状态
function SaveStatus() {
	StatusValue="${2//,/\,}"
	PushStatus="$(echo "${PushStatus}" | sed -e "/^${1}=.*/d" | sed -e "\$i $1='$StatusValue'")"
}

#访问API接口
function APIVisit() {
	APIResult="$(${CURL_CMD} -H "Content-Type:application/json" -X POST --data "${2}" "${1/<URL_API>/$URL_API}")"
	ErrCode="$(expr "${APIResult}" : ".*\"errcode\"\:\([^,]*\),.*")"
	ErrMsg="$(expr "${APIResult}" : ".*\"errmsg\"\:\"\([^\"]*\)\".*")"
	[[ -z "${ErrCode}" ]] && ErrCode="0"
}

#申请访问令牌
function GetAccessToken() {
	ShowStatus "[$(_white "信息")] 开始申请访问令牌。"
	URL_Token="${URL_Token/<WeChatAppID>/$WeChatAppID}"
	URL_Token="${URL_Token/<WeChatAppSecret>/$WeChatAppSecret}"
	APIVisit "${URL_Token}"
	AccessToken="$(expr "${APIResult}" : ".*\"access_token\"\:\"\([^\"]*\)\".*")"
	ExpiresIn="$(expr "${APIResult}" : ".*\"expires_in\"\:\([0-9]*\).*")"
	[[ "${ErrCode}" -ne "0" ]] && ShowStatus "[$(_red "错误")] 申请访问令牌失败！" && exit 3
	ShowStatus "[$(_green "成功")] 申请访问令牌成功。"
	SaveStatus "AccessToken" "${AccessToken}"
	SaveStatus "ExpiresIn" "${ExpiresIn}"
	SaveStatus "TimeStamp" "$(date +%s)"
}

#读取访问令牌
function ReadAccessToken() {
	[[ -r "${StatusFile}" ]] && PushStatus="$(cat "${StatusFile}")"
	AccessToken="$(ReadStatus 'AccessToken')"
	ExpiresIn="$(ReadStatus 'ExpiresIn' '7000')"
	TimeStamp="$(ReadStatus 'TimeStamp' '0')"
	if [[ "$(( $(date +%s) - ${TimeStamp} ))" -gt 7000 ]]; then
		ShowStatus "[$(_yellow "提醒")] 访问令牌已过期，需要重新申请。"
		GetAccessToken
	fi
}

#获取API结果
function GetAPIResult() {
	try="0"
	while [[ "${try}" -le "${Retry}" ]]; do
		try="$((${try}+1))"
		[[ -n "${Status}" ]] && ShowStatus "${Status}" && sleep ${Interval}
		APIVisit "${1/<AccessToken>/$AccessToken}" "${2}"
		Status=""
		case "${ErrCode}" in
		-1)
			Status="[$(_yellow "警告")] 服务器正忙，正在重试。"
			;;
		0)
			break
			;;
		40001|40014|41001|42001)
			ShowStatus "[$(_yellow "警告")] 访问令牌有误，正在重新申请。"
			GetAccessToken
			;;
		*)
			ShowStatus "[$(_red "错误") $(_white "${ErrCode}")] ${ErrMsg}" && exit 2
			;;
		esac
	done
}

#重置接口限额
function ClearQuota() {
	ReadAccessToken
	ShowStatus "[$(_white "信息")] 开始重置接口调用次数。"
	GetAPIResult "${URL_Quota}" "{\"appid\":\"$WeChatAppID\"}"
	if [[ "${ErrCode}" -eq "0" ]]; then
		ShowStatus "[$(_green "成功")] 重置接口调用次数成功。"
		exit
	else
		ShowStatus "[$(_red "错误")] 重置接口调用次数失败！"
		exit -1
	fi
}

#获取用户列表
function GetUserList() {
	ShowStatus "[$(_white "信息")] 开始获取用户列表。"
	GetAPIResult "${URL_User}"
	[[ "${ErrCode}" -ne "0" ]] && ShowStatus "[$(_red "错误")] 获取用户列表失败！" && exit 4
	WeChatUserList="$(expr "${APIResult}" : ".*\"openid\"\:\[\"\([^]]*\)\"].*")"
	WeChatUserList=${WeChatUserList//\"/ }
}

#推送模板消息
function PushMessage() {
	ShowStatus "[$(_white "信息")] 向用户\"${Openid}\"推送消息。"
	PostData="${PostStructure}"
	PostData="${PostData/<WeChatTemplateID>/$WeChatTemplateID}"
	PostData="${PostData/<Openid>/$Openid}"
	PostData="${PostData/<Message>/$Message}"
	PostData="${PostData/<Url>/$Url}"
	GetAPIResult "${URL_Message}" "${PostData}"
	if [[ "${ErrCode}" -eq "0" ]]; then
		SendSuccessList="${SendSuccessList}\n\t${Openid}"
		WeChatUserList="${WeChatUserList},${Openid}"
	else
		SendFailedList="${SendFailedList}\n\t${Openid}"
	fi
}
#加载默认参数
Initialization
#解析命令参数
ParseParameters "$@"
#加载配置文件
GetConfig
#加载命令参数
GetParameters
#读取访问令牌
ReadAccessToken

[[ -z "${WeChatUserList}" ]] && WeChatUserList="$(ReadStatus 'UserList')"
[[ -z "${WeChatUserList}" ]] && GetUserList
UserList="${WeChatUserList//,/ }"
WeChatUserList=""
for Openid in ${UserList}; do
	PushMessage
done
SaveStatus "UserList" "${WeChatUserList#,}"
echo "${PushStatus}" > ${StatusFile} 2> /dev/null
chmod 777 "${StatusFile}" 2> /dev/null

if [[ -z "${SendFailedList}" ]];then
	ShowStatus "[$(_green "成功")] 向所有用户推送消息成功。"
	ExitCode="0"
elif [[ -z "${SendSuccessList}" ]]; then
	ShowStatus "[$(_red "失败")] 向所有用户推送消息失败。"
	ExitCode="5"
else
	ShowStatus "[$(_green "成功")] 推送消息成功的用户: ${SendSuccessList}"
	ShowStatus "[$(_yellow "提醒")] 推送消息失败的用户: ${SendFailedList}"
	ExitCode="0"
fi
exit "${ExitCode}"